<!DOCTYPE html>



  


<html class="theme-next gemini use-motion" lang="zh-Hans">
<head><meta name="generator" content="Hexo 3.9.0">
  <meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<meta name="theme-color" content="#222">









<meta http-equiv="Cache-Control" content="no-transform">
<meta http-equiv="Cache-Control" content="no-siteapp">
















  
  
  <link href="/lib/fancybox/source/jquery.fancybox.css?v=2.1.5" rel="stylesheet" type="text/css">







<link href="/lib/font-awesome/css/font-awesome.min.css?v=4.6.2" rel="stylesheet" type="text/css">

<link href="/css/main.css?v=5.1.4" rel="stylesheet" type="text/css">


  <link rel="apple-touch-icon" sizes="180x180" href="/images/apple-touch-icon-hemisu.png?v=5.1.4">


  <link rel="icon" type="image/png" sizes="32x32" href="/images/favicon-32x32-hemisu.png?v=5.1.4">


  <link rel="icon" type="image/png" sizes="16x16" href="/images/favicon-16x16-hemisu.png?v=5.1.4">


  <link rel="mask-icon" href="/images/logo.svg?v=5.1.4" color="#222">





  <meta name="keywords" content="hemisu">










<meta name="description" content="前言本篇起因源一个问题，Promise.all的限制并发数是多少？ 这个可以通过 V8 的测试用例得知这个数字不超过 2097151： 123456789const a = new Array(2 ** 21 - 1);const p = Promise.resolve(1);for (let i = 0; i &amp;lt; a.length; ++i) a[i] = p;testAsync(asse">
<meta name="keywords" content="web, 前端, 生活, 分享">
<meta property="og:type" content="article">
<meta property="og:title" content="js异步初探 - 图解并发控制">
<meta property="og:url" content="http://www.hemisu.com/2020/06/20/promise-pool/index.html">
<meta property="og:site_name" content="何米酥`s Blog">
<meta property="og:description" content="前言本篇起因源一个问题，Promise.all的限制并发数是多少？ 这个可以通过 V8 的测试用例得知这个数字不超过 2097151： 123456789const a = new Array(2 ** 21 - 1);const p = Promise.resolve(1);for (let i = 0; i &amp;lt; a.length; ++i) a[i] = p;testAsync(asse">
<meta property="og:locale" content="zh-Hans">
<meta property="og:image" content="http://www.hemisu.com/2020/06/20/promise-pool/1.测试用例.png">
<meta property="og:image" content="http://www.hemisu.com/2020/06/20/promise-pool/2.简单实现.png">
<meta property="og:updated_time" content="2020-09-27T05:22:33.736Z">
<meta name="twitter:card" content="summary">
<meta name="twitter:title" content="js异步初探 - 图解并发控制">
<meta name="twitter:description" content="前言本篇起因源一个问题，Promise.all的限制并发数是多少？ 这个可以通过 V8 的测试用例得知这个数字不超过 2097151： 123456789const a = new Array(2 ** 21 - 1);const p = Promise.resolve(1);for (let i = 0; i &amp;lt; a.length; ++i) a[i] = p;testAsync(asse">
<meta name="twitter:image" content="http://www.hemisu.com/2020/06/20/promise-pool/1.测试用例.png">



<script type="text/javascript" id="hexo.configurations">
  var NexT = window.NexT || {};
  var CONFIG = {
    root: '/',
    scheme: 'Gemini',
    version: '5.1.4',
    sidebar: {"position":"left","display":"post","offset":12,"b2t":true,"scrollpercent":true,"onmobile":false},
    fancybox: true,
    tabs: true,
    motion: {"enable":true,"async":false,"transition":{"post_block":"fadeIn","post_header":"slideDownIn","post_body":"slideDownIn","coll_header":"slideLeftIn","sidebar":"slideUpIn"}},
    duoshuo: {
      userId: '0',
      author: '博主'
    },
    algolia: {
      applicationID: '',
      apiKey: '',
      indexName: '',
      hits: {"per_page":10},
      labels: {"input_placeholder":"Search for Posts","hits_empty":"We didn't find any results for the search: ${query}","hits_stats":"${hits} results found in ${time} ms"}
    }
  };
</script>



  <link rel="canonical" href="http://www.hemisu.com/2020/06/20/promise-pool/">





  <title>js异步初探 - 图解并发控制 | 何米酥`s Blog</title>
  








  <!-- Hotjar Tracking Code for www.hemisu.com -->
  <script>
      (function(h,o,t,j,a,r){
          h.hj=h.hj||function(){(h.hj.q=h.hj.q||[]).push(arguments)};
          h._hjSettings={hjid:1933736,hjsv:6};
          a=o.getElementsByTagName('head')[0];
          r=o.createElement('script');r.async=1;
          r.src=t+h._hjSettings.hjid+j+h._hjSettings.hjsv;
          a.appendChild(r);
      })(window,document,'https://static.hotjar.com/c/hotjar-','.js?sv=');
  </script>
</head>

<body itemscope itemtype="http://schema.org/WebPage" lang="zh-Hans">

  
  
    
  

  <div class="container sidebar-position-left page-post-detail">
    <div class="headband"></div>

    <header id="header" class="header" itemscope itemtype="http://schema.org/WPHeader">
      <div class="header-inner"><div class="site-brand-wrapper">
  <div class="site-meta ">
    

    <div class="custom-logo-site-title">
      <a href="/" class="brand" rel="start">
        <span class="logo-line-before"><i></i></span>
        <span class="site-title">何米酥`s Blog</span>
        <span class="logo-line-after"><i></i></span>
      </a>
    </div>
      
        <p class="site-subtitle">EFE</p>
      
  </div>

  <div class="site-nav-toggle">
    <button>
      <span class="btn-bar"></span>
      <span class="btn-bar"></span>
      <span class="btn-bar"></span>
    </button>
  </div>
</div>

<nav class="site-nav">
  

  
    <ul id="menu" class="menu">
      
        
        <li class="menu-item menu-item-home">
          <a href="/" rel="section">
            
              <i class="menu-item-icon fa fa-fw fa-home"></i> <br>
            
            首页
          </a>
        </li>
      
        
        <li class="menu-item menu-item-about">
          <a href="/about/" rel="section">
            
              <i class="menu-item-icon fa fa-fw fa-user"></i> <br>
            
            关于
          </a>
        </li>
      
        
        <li class="menu-item menu-item-tags">
          <a href="/tags/" rel="section">
            
              <i class="menu-item-icon fa fa-fw fa-tags"></i> <br>
            
            标签
          </a>
        </li>
      
        
        <li class="menu-item menu-item-categories">
          <a href="/categories/" rel="section">
            
              <i class="menu-item-icon fa fa-fw fa-th"></i> <br>
            
            分类
          </a>
        </li>
      
        
        <li class="menu-item menu-item-archives">
          <a href="/archives/" rel="section">
            
              <i class="menu-item-icon fa fa-fw fa-archive"></i> <br>
            
            归档
          </a>
        </li>
      

      
    </ul>
  

  
</nav>



 </div>
    </header>

    <main id="main" class="main">
      <div class="main-inner">
        <div class="content-wrap">
          <div id="content" class="content">
            

  <div id="posts" class="posts-expand">
    

  

  
  
  

  <article class="post post-type-normal" itemscope itemtype="http://schema.org/Article">
  
  
  
  <div class="post-block">
    <link itemprop="mainEntityOfPage" href="http://www.hemisu.com/2020/06/20/promise-pool/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="name" content="何米酥">
      <meta itemprop="description" content>
      <meta itemprop="image" content="/images/avatar.jpg">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="何米酥`s Blog">
    </span>

    
      <header class="post-header">

        
        
          <h1 class="post-title" itemprop="name headline">js异步初探 - 图解并发控制</h1>
        

        <div class="post-meta">
          <span class="post-time">
            
              <span class="post-meta-item-icon">
                <i class="fa fa-calendar-o"></i>
              </span>
              
                <span class="post-meta-item-text">发表于</span>
              
              <time title="创建于" itemprop="dateCreated datePublished" datetime="2020-06-20T01:57:31+00:00">
                2020-06-20
              </time>
            

            

            
          </span>

          

          
            
          

          
          

          

          

          

        </div>
      </header>
    

    
    
    
    <div class="post-body" itemprop="articleBody">

      
      

      
        <h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>本篇起因源一个问题，Promise.all的限制并发数是多少？</p>
<p>这个可以通过 <code>V8</code> 的<a href="https://github.com/v8/v8/blob/4b9b23521e6fd42373ebbcb20ebe03bf445494f9/test/mjsunit/es6/promise-all-overflow-1.js#L9-L12" target="_blank" rel="noopener">测试用例</a>得知这个数字不超过 <code>2097151</code>：</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> a = <span class="keyword">new</span> <span class="built_in">Array</span>(<span class="number">2</span> ** <span class="number">21</span> - <span class="number">1</span>);</span><br><span class="line"><span class="keyword">const</span> p = <span class="built_in">Promise</span>.resolve(<span class="number">1</span>);</span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">0</span>; i &lt; a.length; ++i) a[i] = p;</span><br><span class="line">testAsync(<span class="function"><span class="params">assert</span> =&gt;</span> &#123;</span><br><span class="line">  assert.plan(<span class="number">1</span>);</span><br><span class="line">  <span class="built_in">Promise</span>.all(a).then(assert.unreachable, reason =&gt; &#123;</span><br><span class="line">    assert.equals(<span class="literal">true</span>, reason <span class="keyword">instanceof</span> <span class="built_in">RangeError</span>);</span><br><span class="line">  &#125;);</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>
<p>平时在社区中，我们也偶尔会看到这样一道题：</p>
<blockquote>
<p>请实现如下函数，可以批量请求数据,所有的 url 地址在 urls 参数中，同时可以通过 max 参数控制请求的并发数，当所有请求结束之后，需要执行 callback 回调函数，发送请求的函数可以直接使用 fetch 即可</p>
</blockquote>
<p>这道题我们发现，发送请求的函数使用 <code>fetch</code> 可知这是在在浏览器环境中；浏览器其实对同一域名的并发数量有所限制，比如 <code>Chrome</code> 就限制了 <code>6</code> 个，所以这里的 <code>max</code> 我们可以认为是不超过 <code>10</code> 个。解决这类限制我们可以通过多域名的形式优化。<br><a id="more"></a></p>
<p>以目前的网络环境，我们恨不得增加浏览器的单域名并发请求上限。这么看来这道题的实用性很小喽？</p>
<p>然而，在工作中遇到了一个这样问题，让我发现这题有它的应用场景。</p>
<blockquote>
<p>发送邮件和IM消息需要调用三方提供的API接口，而这个接口分别限制了150/min和600/min的上限</p>
</blockquote>
<p>假设我们不作限制，直接使用 <code>promise.all</code> 实例化所有的消息发起请求，假设接口请求返回时间是 <code>1s</code> 的话， 直接突破了 <code>6 * 60 &gt; 150</code>  的限制导致后续的请求失效。</p>
<p>首先我们定制一个测试用例，限制并发数 <code>limit</code> 为 <code>2</code></p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> results = [];</span><br><span class="line"><span class="keyword">const</span> timeout = <span class="function"><span class="params">i</span> =&gt;</span></span><br><span class="line">  <span class="keyword">new</span> <span class="built_in">Promise</span>(<span class="function"><span class="params">resolve</span> =&gt;</span></span><br><span class="line">    setTimeout(<span class="function"><span class="params">()</span> =&gt;</span> &#123;</span><br><span class="line">      results.push(i);</span><br><span class="line">      resolve();</span><br><span class="line">    &#125;, i)</span><br><span class="line">  ).then(<span class="function"><span class="params">()</span> =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> i</span><br><span class="line">  &#125;);</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> urls = [<span class="number">100</span>, <span class="number">500</span>, <span class="number">300</span>, <span class="number">200</span>]</span><br></pre></td></tr></table></figure>
<p>那么执行之后会有以下两个结果：</p>
<ol>
<li>如果没有限制成功，得到的结果应该是 <code>[100, 200, 300, 500]</code></li>
<li>如果限制并发数成功，得到的结果应该是 <code>[100, 300, 500, 200]</code></li>
</ol>
<p>如图所示<br><img src="1.测试用例.png" alt="IMAGE"></p>
<h2 id="尝试1-失败的第一次"><a href="#尝试1-失败的第一次" class="headerlink" title="尝试1. 失败的第一次"></a>尝试1. 失败的第一次</h2><p><a href="https://runkit.com/hemisu/concurrency-limit-1" target="_blank" rel="noopener">在 runkit 中运行</a><br>我们可以利用 <code>Promise.all</code> 配合 <code>ES7</code> 的 <code>async</code> 语法实现一个简单的并发限制</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">async</span> <span class="function"><span class="keyword">function</span> <span class="title">main</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">  <span class="keyword">for</span>(<span class="keyword">let</span> i = <span class="number">0</span>; i &lt; urls.length; i += <span class="number">2</span>) &#123;</span><br><span class="line">    <span class="keyword">await</span> <span class="built_in">Promise</span>.all(urls.slice(i, i + <span class="number">2</span>).map(<span class="function"><span class="params">_</span> =&gt;</span> timeout(_)))</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="built_in">console</span>.log(results)</span><br><span class="line">&#125;</span><br><span class="line">main()</span><br><span class="line"></span><br><span class="line"><span class="comment">// Array(4) [100, 500, 200, 300]</span></span><br></pre></td></tr></table></figure>
<p>执行得到的结果是 <code>Array(4) [100, 500, 200, 300]</code>，和我们前面提到的不符鸭，画个图康康</p>
<p><img src="2.简单实现.png" alt="IMAGE"></p>
<p>很显然，这个是不符合我们要求的；我们不可能等到这两个通道里最晚的执行完毕后再发起下一波请求。</p>
<h2 id="尝试2-限制通道数"><a href="#尝试2-限制通道数" class="headerlink" title="尝试2. 限制通道数"></a>尝试2. 限制通道数</h2><p><a href="https://runkit.com/hemisu/concurrency-limit-2" target="_blank" rel="noopener">在 runkit 中运行</a></p>
<p>既然我们需要在 <code>100</code> 执行完后立刻放入下一个 <code>500</code>，我们就需要抛弃这个 <code>Promise.all</code> 来进行一次尝试了</p>
<p>这里我们可以假想有 <code>2</code> 个通道，开始时我们先填满这 <code>2</code> 个通道，然后我们对其中运行的 <code>1</code> 和 <code>5</code> 注册一个回调函数，它可以做以下两件事：</p>
<ol>
<li>如果发现待执行的队列还有需要执行的，把它放入队列（如 <code>1</code> 执行完后就应当把 <code>3</code> 放入）</li>
<li>如果发现执行完毕（此处应该有一个计数，统计当前已经请求完毕），那么不再取出数据并且决议完成（或者执行完成后的回调）</li>
</ol>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">limitRun</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">  <span class="keyword">let</span> index = <span class="number">0</span>, cnt = <span class="number">0</span>; <span class="comment">// 计数君</span></span><br><span class="line">  <span class="keyword">let</span> idx = <span class="number">2</span>; <span class="comment">// 通道数</span></span><br><span class="line">  <span class="function"><span class="keyword">function</span> <span class="title">_request</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">    <span class="keyword">while</span>(idx &gt; <span class="number">0</span> &amp;&amp; cnt &lt; <span class="number">3</span>) &#123;</span><br><span class="line">      <span class="keyword">const</span> item = urls[index++] <span class="comment">// 取出一个用于请求</span></span><br><span class="line">      idx-- <span class="comment">// 占用通道</span></span><br><span class="line">      timeout(item)</span><br><span class="line">      .finally(<span class="function"><span class="params">()</span> =&gt;</span> &#123; <span class="comment">// 注册一个回调函数，做上面提到的两件事</span></span><br><span class="line">        cnt++ <span class="comment">// 计数+1</span></span><br><span class="line">        idx++ <span class="comment">// 释放通道</span></span><br><span class="line">        <span class="keyword">if</span> (cnt === <span class="number">4</span>) &#123;</span><br><span class="line">          <span class="comment">// 2. 如果发现执行完毕（此处应该有一个计数，统计当前已经请求完毕），那么不再取出数据并且决议完成（或者执行完成后的回调）</span></span><br><span class="line">          <span class="comment">// do something..</span></span><br><span class="line">          <span class="built_in">console</span>.log(<span class="string">'执行完毕'</span>, results)</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">          <span class="comment">// 1. 如果发现待执行的队列还有需要执行的，把它放入队列（如 `1` 执行完后就应当把 `3` 放入）</span></span><br><span class="line">          _request() <span class="comment">// 递归执行</span></span><br><span class="line">        &#125;</span><br><span class="line">      &#125;)</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  _request()</span><br><span class="line">&#125;</span><br><span class="line">limitRun()</span><br><span class="line"><span class="comment">// Array(4) [100, 300, 500, 200]</span></span><br></pre></td></tr></table></figure>
<p>通过这样的方式，我们第一次得到了正确答案。简单的抽象一下，我们就可以得到一个工具函数。</p>
<h2 id="尝试3-第一次抽象"><a href="#尝试3-第一次抽象" class="headerlink" title="尝试3. 第一次抽象"></a>尝试3. 第一次抽象</h2><p><a href="https://runkit.com/hemisu/concurrency-limit-3" target="_blank" rel="noopener">在 runkit 中运行</a></p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 并发工具函数</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param <span class="type">&#123;any[]&#125;</span> </span>queue 待处理队列</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param <span class="type">&#123;() =&gt; Promise&lt;any&gt;&#125;</span> </span>fn 异步函数，返回一个promise</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param <span class="type">&#123;Number&#125;</span> </span>max 并发上限</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param <span class="type">&#123;() =&gt; any&#125;</span> </span>callback 回调</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">sendRequestIdle</span> (<span class="params">queue, fn = (</span>) =&gt; <span class="title">Promise</span>.<span class="title">resolve</span>(<span class="params"></span>), <span class="title">max</span> = 1, <span class="title">callback</span>) </span>&#123;</span><br><span class="line">  <span class="keyword">const</span> len = queue.length;</span><br><span class="line">  <span class="keyword">let</span> idx = <span class="number">0</span>, cnt = <span class="number">0</span>;</span><br><span class="line">  <span class="function"><span class="keyword">function</span> <span class="title">_request</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">    <span class="keyword">while</span>(idx &lt; len &amp;&amp; max &gt; <span class="number">0</span>) &#123;</span><br><span class="line">      max--</span><br><span class="line">      fn(queue[idx++]).finally(<span class="function"><span class="params">()</span> =&gt;</span> &#123;</span><br><span class="line">        max++</span><br><span class="line">        cnt++</span><br><span class="line">        <span class="keyword">if</span>(len === cnt) &#123;</span><br><span class="line">          <span class="keyword">return</span> callback()</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">          _request()</span><br><span class="line">        &#125;</span><br><span class="line">      &#125;)</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  _request()</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">sendRequestIdle(urls, timeout, <span class="number">2</span>, () =&gt; &#123;<span class="built_in">console</span>.log(results)&#125;)</span><br></pre></td></tr></table></figure>
<h2 id="尝试4-进一步优化"><a href="#尝试4-进一步优化" class="headerlink" title="尝试4. 进一步优化"></a>尝试4. 进一步优化</h2><p><a href="https://runkit.com/hemisu/concurrency-limit4" target="_blank" rel="noopener">在 runkit 中运行</a></p>
<p>其实到了上一步，已经满足了我们的需求。我们可以更进一步，把递归优化成迭代的形式。</p>
<p>这一步的工作是为了方便我们更进一步，可以更方便的去管理异步队列，抽象为一个个池子单独运行。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">LimitPool</span> </span>&#123;</span><br><span class="line">  <span class="keyword">constructor</span>(max) &#123;</span><br><span class="line">    <span class="keyword">this</span>._max = max</span><br><span class="line">    <span class="keyword">this</span>._idle = <span class="number">0</span></span><br><span class="line">    <span class="keyword">this</span>._queue = []</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  call(fn, ...args) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">new</span> <span class="built_in">Promise</span>(<span class="function">(<span class="params">resolve, reject</span>) =&gt;</span> &#123;</span><br><span class="line">      <span class="keyword">const</span> task = <span class="keyword">this</span>._createTask(fn, args, resolve, reject)</span><br><span class="line">      <span class="keyword">if</span>(<span class="keyword">this</span>._idle &gt;= <span class="keyword">this</span>._max) &#123;</span><br><span class="line">        <span class="comment">// 大于目前通道数 放入队列中</span></span><br><span class="line">        <span class="keyword">this</span>._queue.push(task)</span><br><span class="line">      &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        task()</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;)</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  _createTask(fn, args, _resolve, _reject) &#123;</span><br><span class="line">    <span class="comment">// 惰性计算 如果返回，会直接计算fn，Promise的构造函数是直接运行的，不会异步执行</span></span><br><span class="line">    <span class="keyword">return</span> <span class="function"><span class="params">()</span> =&gt;</span> &#123;</span><br><span class="line">      fn(...args)</span><br><span class="line">      .then(_resolve)</span><br><span class="line">      .catch(_reject)</span><br><span class="line">      .finally(<span class="function"><span class="params">()</span> =&gt;</span> &#123;</span><br><span class="line">        <span class="keyword">this</span>._idle--</span><br><span class="line">        <span class="keyword">if</span> (<span class="keyword">this</span>._queue.length) &#123;</span><br><span class="line">          <span class="keyword">const</span> task = <span class="keyword">this</span>._queue.shift()</span><br><span class="line">          task()</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">          <span class="comment">// console.log('队列清空完毕')</span></span><br><span class="line">        &#125;</span><br><span class="line">      &#125;)</span><br><span class="line">      <span class="keyword">this</span>._idle++</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 初始化请求池</span></span><br><span class="line"><span class="keyword">const</span> limitPool = <span class="keyword">new</span> LimitPool(<span class="number">2</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment">// 请求就完事了，根本不慌</span></span><br><span class="line"><span class="keyword">for</span>(<span class="keyword">let</span> i <span class="keyword">of</span> urls) &#123;</span><br><span class="line">  <span class="built_in">console</span>.log(i)</span><br><span class="line">  limitPool.call(timeout, i).then(<span class="function"><span class="params">()</span> =&gt;</span> <span class="built_in">console</span>.log(results))</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 输出结果：</span></span><br><span class="line"><span class="comment"> * Array(1) [100]</span></span><br><span class="line"><span class="comment"> * Array(2) [100, 300]</span></span><br><span class="line"><span class="comment"> * Array(3) [100, 300, 500]</span></span><br><span class="line"><span class="comment"> * Array(4) [100, 300, 500, 200]</span></span><br><span class="line"><span class="comment"> */</span></span><br></pre></td></tr></table></figure>
<p>此条参考的是 <a href="https://github.com/leejialing/limit-promise/blob/master/lib/LimitPromise.js" target="_blank" rel="noopener">limit-promise</a></p>
<p>后续按照下载量排名讲解几个社区内的并发控制库</p>
<p><a href="https://www.npmjs.com/package/tiny-async-pool" target="_blank" rel="noopener">async-pool</a>、<a href="https://www.npmjs.com/package/p-limit" target="_blank" rel="noopener">p-limit</a></p>
<h2 id="读三方库"><a href="#读三方库" class="headerlink" title="读三方库"></a>读三方库</h2><p>接下来解读俩个社区内的并发控制库</p>
<ul>
<li><a href="https://www.npmjs.com/package/tiny-async-pool" target="_blank" rel="noopener">tiny-async-pool</a></li>
<li><a href="https://www.npmjs.com/package/p-limit" target="_blank" rel="noopener">p-limit</a></li>
</ul>
<h3 id="1-tiny-async-pool"><a href="#1-tiny-async-pool" class="headerlink" title="1. tiny-async-pool"></a>1. tiny-async-pool</h3><p><a href="https://runkit.com/hemisu/tiny-async-pool-es7" target="_blank" rel="noopener">在 runkit 中运行</a></p>
<p>第一个库如名字所示，突出一个小巧。它有<a href="https://github.com/rxaviers/async-pool/tree/master/lib" target="_blank" rel="noopener">两种版本</a>的实现。</p>
<h4 id="ES7版本"><a href="#ES7版本" class="headerlink" title="ES7版本"></a>ES7版本</h4><p>该版本使用 <code>Promise.all</code> 和 <code>Promise.race</code> 组合进行实现，单纯的使用 <code>Promise.all</code> 的结果已经在上文中展现了，无法满足我们的需求。</p>
<p>此处使用 <code>Promise.all</code> 的作用仅仅是提取结果，阻塞后续请求的底层能力是由 <code>for of loop</code> + <code>async function</code> 语法糖中调用的 <code>Generator</code> 作为协程提供的。</p>
<p>这里值得注意的是两次调用 <code>.then</code> ，是会返回的不同的promise对象<a href="http://liubin.org/promises-book/#then-return-new-promise" target="_blank" rel="noopener">参考</a>。</p>
<p>以下是 <code>ES7</code> 版本的解读<br><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">async</span> <span class="function"><span class="keyword">function</span> <span class="title">asyncPool</span>(<span class="params">poolLimit, array, iteratorFn</span>) </span>&#123;</span><br><span class="line">  <span class="comment">// 存储结果队列</span></span><br><span class="line">  <span class="keyword">const</span> ret = [];</span><br><span class="line">  <span class="comment">// 存储执行队列，该队列长度不会超过 poolLimit 的大小</span></span><br><span class="line">  <span class="keyword">const</span> executing = [];</span><br><span class="line"></span><br><span class="line">  <span class="keyword">for</span> (<span class="keyword">const</span> item <span class="keyword">of</span> array) &#123;</span><br><span class="line">    <span class="comment">// 实例化，如果有请求则在此处进行</span></span><br><span class="line">    <span class="keyword">const</span> p = <span class="built_in">Promise</span>.resolve().then(<span class="function"><span class="params">()</span> =&gt;</span> iteratorFn(item, array));</span><br><span class="line">    <span class="comment">// 将注册 iteratorFn 回调的 Promise 放入结果队列</span></span><br><span class="line">    ret.push(p);</span><br><span class="line">    <span class="comment">// 新的 Promise 对象，执行后会将自己从执行队列中剔除</span></span><br><span class="line">    <span class="keyword">const</span> e = p.then(<span class="function"><span class="params">()</span> =&gt;</span> executing.splice(executing.indexOf(e), <span class="number">1</span>));</span><br><span class="line">    <span class="comment">// 加入执行队列</span></span><br><span class="line">    executing.push(e);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 如果执行队列已满</span></span><br><span class="line">    <span class="keyword">if</span> (executing.length &gt;= poolLimit) &#123;</span><br><span class="line">      <span class="comment">// 协程阻塞，直到一个任务（请求）成功或者失败</span></span><br><span class="line">      <span class="comment">// 再执行下一轮 for of</span></span><br><span class="line">      <span class="keyword">await</span> <span class="built_in">Promise</span>.race(executing);</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="comment">// 类似于 Monad 中将 Unit :: a -&gt; monad a 的 a 取出</span></span><br><span class="line">  <span class="comment">// 通过 Promise.all 将结果数组 ret 中的 Promise 结果提取</span></span><br><span class="line">  <span class="keyword">return</span> <span class="built_in">Promise</span>.all(ret);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<h4 id="ES6版本"><a href="#ES6版本" class="headerlink" title="ES6版本"></a>ES6版本</h4><p><a href="https://runkit.com/hemisu/tiny-async-pool-es6" target="_blank" rel="noopener">在 runkit 中运行</a></p>
<p>在理解了上述 <code>ES7</code> 的版本之后，我们看ES6的版本会轻松很多；</p>
<p>核心的两个数组 <code>ret</code> 和 <code>executing</code> 的用处已经在上面解释过了</p>
<p>下面仅注释与上文不同的地方：</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">asyncPool</span>(<span class="params">poolLimit, array, iteratorFn</span>) </span>&#123;</span><br><span class="line">  <span class="comment">// 记录执行到第 i 个任务</span></span><br><span class="line">  <span class="keyword">let</span> i = <span class="number">0</span>;</span><br><span class="line">  <span class="keyword">const</span> ret = [];</span><br><span class="line">  <span class="keyword">const</span> executing = [];</span><br><span class="line">  <span class="keyword">const</span> enqueue = <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">    <span class="comment">// 递归出口： 如果任务执行完，返回 Resolved Promise</span></span><br><span class="line">    <span class="keyword">if</span> (i === array.length) &#123;</span><br><span class="line">      <span class="keyword">return</span> <span class="built_in">Promise</span>.resolve();</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">const</span> item = array[i++];</span><br><span class="line">    <span class="keyword">const</span> p = <span class="built_in">Promise</span>.resolve().then(<span class="function"><span class="params">()</span> =&gt;</span> iteratorFn(item, array));</span><br><span class="line">    ret.push(p);</span><br><span class="line">    <span class="keyword">const</span> e = p.then(<span class="function"><span class="params">()</span> =&gt;</span> executing.splice(executing.indexOf(e), <span class="number">1</span>));</span><br><span class="line">    executing.push(e);</span><br><span class="line">    <span class="comment">// 这里就是与上面不同的第一处关键点</span></span><br><span class="line">    <span class="comment">// r 默认声明为 Resolved Promise</span></span><br><span class="line">    <span class="comment">// 当执行队列 executing 被占满后，</span></span><br><span class="line">    <span class="comment">// r 会被替换为 Promise.race 等待其中一个任务的完成</span></span><br><span class="line">    <span class="keyword">let</span> r = <span class="built_in">Promise</span>.resolve();</span><br><span class="line">    <span class="keyword">if</span> (executing.length &gt;= poolLimit) &#123;</span><br><span class="line">      r = <span class="built_in">Promise</span>.race(executing);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 同时注册回调，递归开始</span></span><br><span class="line">    <span class="keyword">return</span> r.then(<span class="function"><span class="params">()</span> =&gt;</span> enqueue());</span><br><span class="line">  &#125;;</span><br><span class="line">  <span class="keyword">return</span> enqueue().then(<span class="function"><span class="params">()</span> =&gt;</span> <span class="built_in">Promise</span>.all(ret));</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<h3 id="2-p-limit"><a href="#2-p-limit" class="headerlink" title="2. p-limit"></a>2. p-limit</h3><p><a href="https://runkit.com/hemisu/p-limit" target="_blank" rel="noopener">在 runkit 中运行</a></p>
<p>这是个周下载快 3 千万的库 🤔 (携带了 <code>p-</code> 一家子)，在读这个库之前我们先过一下前置知识。</p>
<p>平时我们使用的 <code>Promise ES</code> 版本提供了这么些个实例方法给我们折腾：</p>
<ul>
<li><code>Promise.all</code></li>
<li><code>Promise.allSetteld</code> (还没普及，babel 一下又不是不能用)</li>
<li><code>Promise.race</code></li>
<li><code>Promise.any</code> 对比 <code>race</code> 它返回第一个 <code>resolve</code> 的值 (支持度比 <code>allSetteld</code> 还低，babel 一下又不是不能用) </li>
</ul>
<p>可是在这个 <code>p-limit</code> 中调用了一个 <code>p-try</code> 的包（警觉。</p>
<p>看完了挂在 README 的这篇 <a href="http://cryto.net/~joepie91/blog/2016/05/11/what-is-promise-try-and-why-does-it-matter/" target="_blank" rel="noopener">What is Promise.try, and why does it matter?</a> ，文章表达了以下几个 <code>Promise.try</code> 所带来的好处，详情可以进去see一下：</p>
<ol>
<li>更好地错误处理： 通过它可以把同步错误转为异步错误，统一处理避免一堆 <code>try..catch</code></li>
<li>更好地交互： 不管你用的是哪种 Promise (p, bluebird…) 都可以转变为你目前用的 <code>Promise</code> 来执行调用链</li>
<li>更好地see： 这个见仁见智，我觉得问题不大 🐶</li>
</ol>
<p>整了这么多高大上的我以为会看到又一份精妙代码，然后啃读，结果就一行整完了：</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> promiseTry = <span class="function">(<span class="params">func, ...arguments_</span>) =&gt;</span> <span class="keyword">new</span> <span class="built_in">Promise</span>(<span class="function"><span class="params">resolve</span> =&gt;</span> resolve(func, ...arguments_))</span><br></pre></td></tr></table></figure>
<p>接下来开始研读 <code>p-limit</code>： 首先从 <code>API</code> 入手，我们发现它先调用了 <code>pLimit(concurrency)</code> 限定了同时请求数目；这个和上文 <strong>尝试4. 进一步优化</strong> 的 <code>limit-promise</code> 有点相似，此处没有实例化对象，返回了一个闭包</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> pLimit = <span class="function"><span class="params">concurrency</span> =&gt;</span> &#123;</span><br><span class="line">  <span class="comment">// 校验参数</span></span><br><span class="line">	<span class="keyword">if</span> (!((<span class="built_in">Number</span>.isInteger(concurrency) || concurrency === <span class="literal">Infinity</span>) &amp;&amp; concurrency &gt; <span class="number">0</span>)) &#123;</span><br><span class="line">		<span class="keyword">return</span> <span class="built_in">Promise</span>.reject(<span class="keyword">new</span> <span class="built_in">TypeError</span>(<span class="string">'Expected `concurrency` to be a number from 1 and up'</span>));</span><br><span class="line">	&#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 待执行队列</span></span><br><span class="line">  <span class="keyword">const</span> queue = [];</span><br><span class="line">  <span class="comment">// 执行中的任务数 对比之前的实现，可以看作通道数 idle</span></span><br><span class="line">	<span class="keyword">let</span> activeCount = <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 执行完毕后的回调函数</span></span><br><span class="line">	<span class="keyword">const</span> next = <span class="function"><span class="params">()</span> =&gt;</span> &#123;</span><br><span class="line">    <span class="comment">// 执行数目--， 等于释放通道</span></span><br><span class="line">		activeCount--;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 如果待执行队列不为空，取出一个继续执行</span></span><br><span class="line">		<span class="keyword">if</span> (queue.length &gt; <span class="number">0</span>) &#123;</span><br><span class="line">			queue.shift()();</span><br><span class="line">		&#125;</span><br><span class="line">	&#125;;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 执行任务</span></span><br><span class="line">	<span class="keyword">const</span> run = <span class="keyword">async</span> (fn, resolve, ...args) =&gt; &#123;</span><br><span class="line">    <span class="comment">// 占用通道</span></span><br><span class="line">    activeCount++;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 此处实例化fn，如果有请求在这里才会正式发起</span></span><br><span class="line">		<span class="keyword">const</span> result = pTry(fn, ...args);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 透传的 generator 中的 Promise resolve</span></span><br><span class="line">		resolve(result);</span><br><span class="line"></span><br><span class="line">		<span class="keyword">try</span> &#123;</span><br><span class="line">			<span class="keyword">await</span> result;</span><br><span class="line">		&#125; <span class="keyword">catch</span>(e) &#123;</span><br><span class="line">      <span class="comment">//</span></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 执行完毕后 释放通道、检查待执行队列</span></span><br><span class="line">		next();</span><br><span class="line">	&#125;;</span><br><span class="line"></span><br><span class="line">	<span class="keyword">const</span> enqueue = <span class="function">(<span class="params">fn, resolve, ...args</span>) =&gt;</span> &#123;</span><br><span class="line">		queue.push(run.bind(<span class="literal">null</span>, fn, resolve, ...args));</span><br><span class="line"></span><br><span class="line">		(<span class="keyword">async</span> () =&gt; &#123;</span><br><span class="line">			<span class="comment">// This function needs to wait until the next microtask before comparing</span></span><br><span class="line">			<span class="comment">// `activeCount` to `concurrency`, because `activeCount` is updated asynchronously</span></span><br><span class="line">			<span class="comment">// when the run function is dequeued and called. The comparison in the if-statement</span></span><br><span class="line">      <span class="comment">// needs to happen asynchronously as well to get an up-to-date value for `activeCount`.</span></span><br><span class="line">      <span class="comment">// 作为隔离多个 run 函数执行的作用，保持原子性</span></span><br><span class="line">      <span class="comment">// 如果没有这一句，同时执行多个 run, 无法确保通道的占用和释放是安全的</span></span><br><span class="line">			<span class="keyword">await</span> <span class="built_in">Promise</span>.resolve();</span><br><span class="line"></span><br><span class="line">			<span class="keyword">if</span> (activeCount &lt; concurrency &amp;&amp; queue.length &gt; <span class="number">0</span>) &#123;</span><br><span class="line">				queue.shift()();</span><br><span class="line">			&#125;</span><br><span class="line">		&#125;)();</span><br><span class="line">	&#125;;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 闭包</span></span><br><span class="line">  <span class="keyword">const</span> generator = <span class="function">(<span class="params">fn, ...args</span>) =&gt;</span> <span class="keyword">new</span> <span class="built_in">Promise</span>(<span class="function"><span class="params">resolve</span> =&gt;</span> enqueue(fn, resolve, ...args));</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 给闭包几个属性和方法</span></span><br><span class="line">	<span class="built_in">Object</span>.defineProperties(generator, &#123;</span><br><span class="line">    <span class="comment">// 返回目前执行的任务数</span></span><br><span class="line">		activeCount: &#123;</span><br><span class="line">			<span class="keyword">get</span>: () =&gt; activeCount</span><br><span class="line">    &#125;,</span><br><span class="line">    // 返回待执行的任务数</span><br><span class="line">		pendingCount: &#123;</span><br><span class="line">			<span class="keyword">get</span>: () =&gt; queue.length</span><br><span class="line">    &#125;,</span><br><span class="line">    // 清空待执行队列</span><br><span class="line">		clearQueue: &#123;</span><br><span class="line">			value: <span class="function"><span class="params">()</span> =&gt;</span> &#123;</span><br><span class="line">				queue.length = <span class="number">0</span>;</span><br><span class="line">			&#125;</span><br><span class="line">		&#125;</span><br><span class="line">	&#125;);</span><br><span class="line"></span><br><span class="line">	<span class="keyword">return</span> generator;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>
<h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>本文从一道模仿HTTP并发请求和工作中的问题出发，简述了JS异步并发控制的方法，并对社区中几个常见库进行解读，希望可以带来不一样的思考，如果大家有不同的想法可以在评论区讨论交流。</p>

      
    </div>
    
    
    

    

    

    

    <footer class="post-footer">
      

      
      
      

      
        <div class="post-nav">
          <div class="post-nav-next post-nav-item">
            
              <a href="/2019/11/04/javascript-concurrency-promise/" rel="next" title="js异步初探 - promise">
                <i class="fa fa-chevron-left"></i> js异步初探 - promise
              </a>
            
          </div>

          <span class="post-nav-divider"></span>

          <div class="post-nav-prev post-nav-item">
            
          </div>
        </div>
      

      
      
    </footer>
  </div>
  
  
  
  </article>



    <div class="post-spread">
      
    </div>
  </div>


          </div>
          


          

  



        </div>
        
          
  
  <div class="sidebar-toggle">
    <div class="sidebar-toggle-line-wrap">
      <span class="sidebar-toggle-line sidebar-toggle-line-first"></span>
      <span class="sidebar-toggle-line sidebar-toggle-line-middle"></span>
      <span class="sidebar-toggle-line sidebar-toggle-line-last"></span>
    </div>
  </div>

  <aside id="sidebar" class="sidebar">
    
    <div class="sidebar-inner">

      

      
        <ul class="sidebar-nav motion-element">
          <li class="sidebar-nav-toc sidebar-nav-active" data-target="post-toc-wrap">
            文章目录
          </li>
          <li class="sidebar-nav-overview" data-target="site-overview-wrap">
            站点概览
          </li>
        </ul>
      

      <section class="site-overview-wrap sidebar-panel">
        <div class="site-overview">
          <div class="site-author motion-element" itemprop="author" itemscope itemtype="http://schema.org/Person">
            
              <img class="site-author-image" itemprop="image" src="/images/avatar.jpg" alt="何米酥">
            
              <p class="site-author-name" itemprop="name">何米酥</p>
              <p class="site-description motion-element" itemprop="description">Just do...</p>
          </div>

          <nav class="site-state motion-element">

            
              <div class="site-state-item site-state-posts">
              
                <a href="/archives/">
              
                  <span class="site-state-item-count">202</span>
                  <span class="site-state-item-name">日志</span>
                </a>
              </div>
            

            
              
              
              <div class="site-state-item site-state-categories">
                <a href="/categories/index.html">
                  <span class="site-state-item-count">8</span>
                  <span class="site-state-item-name">分类</span>
                </a>
              </div>
            

            
              
              
              <div class="site-state-item site-state-tags">
                <a href="/tags/index.html">
                  <span class="site-state-item-count">78</span>
                  <span class="site-state-item-name">标签</span>
                </a>
              </div>
            

          </nav>

          

          
            <div class="links-of-author motion-element">
                
                  <span class="links-of-author-item">
                    <a href="https://github.com/hemisu" target="_blank" title="GitHub">
                      
                        <i class="fa fa-fw fa-github"></i>GitHub</a>
                  </span>
                
                  <span class="links-of-author-item">
                    <a href="https://www.zhihu.com/people/hemisu" target="_blank" title="知乎">
                      
                        <i class="fa fa-fw fa-globe"></i>知乎</a>
                  </span>
                
            </div>
          

          
          

          
          

          

        </div>
      </section>

      
      <!--noindex-->
        <section class="post-toc-wrap motion-element sidebar-panel sidebar-panel-active">
          <div class="post-toc">

            
              
            

            
              <div class="post-toc-content"><ol class="nav"><li class="nav-item nav-level-2"><a class="nav-link" href="#前言"><span class="nav-number">1.</span> <span class="nav-text">前言</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#尝试1-失败的第一次"><span class="nav-number">2.</span> <span class="nav-text">尝试1. 失败的第一次</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#尝试2-限制通道数"><span class="nav-number">3.</span> <span class="nav-text">尝试2. 限制通道数</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#尝试3-第一次抽象"><span class="nav-number">4.</span> <span class="nav-text">尝试3. 第一次抽象</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#尝试4-进一步优化"><span class="nav-number">5.</span> <span class="nav-text">尝试4. 进一步优化</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#读三方库"><span class="nav-number">6.</span> <span class="nav-text">读三方库</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#1-tiny-async-pool"><span class="nav-number">6.1.</span> <span class="nav-text">1. tiny-async-pool</span></a><ol class="nav-child"><li class="nav-item nav-level-4"><a class="nav-link" href="#ES7版本"><span class="nav-number">6.1.1.</span> <span class="nav-text">ES7版本</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#ES6版本"><span class="nav-number">6.1.2.</span> <span class="nav-text">ES6版本</span></a></li></ol></li><li class="nav-item nav-level-3"><a class="nav-link" href="#2-p-limit"><span class="nav-number">6.2.</span> <span class="nav-text">2. p-limit</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#总结"><span class="nav-number">7.</span> <span class="nav-text">总结</span></a></li></ol></div>
            

          </div>
        </section>
      <!--/noindex-->
      

      
        <div class="back-to-top">
          <i class="fa fa-arrow-up"></i>
          
            <span id="scrollpercent"><span>0</span>%</span>
          
        </div>
      

    </div>
  </aside>


        
      </div>
    </main>

    <footer id="footer" class="footer">
      <div class="footer-inner">
        <div class="copyright">&copy; 2015 &mdash; <span itemprop="copyrightYear">2020</span>
  <span class="with-love">
    <i class="fa fa-user"></i>
  </span>
  <span class="author" itemprop="copyrightHolder">何米酥</span>

  
</div>


  <div class="powered-by">由 <a class="theme-link" target="_blank" href="https://hexo.io">Hexo</a> 强力驱动</div>



  <span class="post-meta-divider">|</span>



  <div class="theme-info">主题 &mdash; <a class="theme-link" target="_blank" href="https://github.com/iissnan/hexo-theme-next">NexT.Gemini</a> v5.1.4</div>




        







        
      </div>
    </footer>

    

    

  </div>

  

<script type="text/javascript">
  if (Object.prototype.toString.call(window.Promise) !== '[object Function]') {
    window.Promise = null;
  }
</script>









  












  
  
    <script type="text/javascript" src="/lib/jquery/index.js?v=2.1.3"></script>
  

  
  
    <script type="text/javascript" src="/lib/fastclick/lib/fastclick.min.js?v=1.0.6"></script>
  

  
  
    <script type="text/javascript" src="/lib/jquery_lazyload/jquery.lazyload.js?v=1.9.7"></script>
  

  
  
    <script type="text/javascript" src="/lib/velocity/velocity.min.js?v=1.2.1"></script>
  

  
  
    <script type="text/javascript" src="/lib/velocity/velocity.ui.min.js?v=1.2.1"></script>
  

  
  
    <script type="text/javascript" src="/lib/fancybox/source/jquery.fancybox.pack.js?v=2.1.5"></script>
  


  


  <script type="text/javascript" src="/js/src/utils.js?v=5.1.4"></script>

  <script type="text/javascript" src="/js/src/motion.js?v=5.1.4"></script>



  
  


  <script type="text/javascript" src="/js/src/affix.js?v=5.1.4"></script>

  <script type="text/javascript" src="/js/src/schemes/pisces.js?v=5.1.4"></script>



  
  <script type="text/javascript" src="/js/src/scrollspy.js?v=5.1.4"></script>
<script type="text/javascript" src="/js/src/post-details.js?v=5.1.4"></script>



  


  <script type="text/javascript" src="/js/src/bootstrap.js?v=5.1.4"></script>



  


  




	





  





  












  





  

  

  

  
  

  

  

  

</body>
</html>
